#coding=utf-8

from __future__ import print_function
from sys import getsizeof, stderr
from itertools import chain
from collections import deque
from reprlib import repr
import winsound
import numpy as np
import pytz
import pymongo
import pandas as pd
import calendar as calendar
import pandas as pd
from Core.Instrument import *
import json
import os
import pandas as pd
import random
import decimal
from datetime import *
from xlrd import xldate_as_tuple
from dateutil.relativedelta import relativedelta


class MyEncoder(json.JSONEncoder):
  def default(self, obj):
      # if isinstance(obj, datetime.datetime):
      #     return int(mktime(obj.timetuple()))
      if isinstance(obj, datetime):
          return obj.strftime('%Y-%m-%d %H:%M:%S.%f ')
      elif isinstance(obj, date):
          return obj.strftime('%Y-%m-%d')
      else:
          return json.JSONEncoder.default(self, obj)


def DeleteTimeZone(datetime1):
    return datetime1.replace(tzinfo = None)


def FilterWindDateTimeDirty(dt):
    return datetime(dt.year, dt.month, dt.day, dt.hour,dt.minute, dt.second)


def FutureSymbolToProduct(symbol):
    out = ""
    for s in symbol:
        if s.isdigit():
            return out
        else:
            out = out + s


def FindElement(list, target):
    for element in list:
        found = True
        for k,v in target.items():
            if element[k] != v:
                found = False
                break
        if found == True:
            return element
    return None


def FindInList(list, target):
    for i in range(len(list)):
        element = list[i]
        if element == target:
            return i
    return -1


def FindIndex(dataSeries, datetime, option="Previous", datetimeField="StdDateTime"):
    if len(dataSeries) == 0:
        return None
    index = GetIndex(dataSeries, datetime, 0, len(dataSeries) - 1, 0, option, datetimeField)
    if index == None:
        return None
    if index == -1:
        return None
    if index >= len(dataSeries):
        return None
    return index


def Find(dataSeries, datetime, option="Previous", datetimeField="StdDateTime"):
    index = FindIndex(dataSeries, datetime, option=option, datetimeField=datetimeField)
    #
    if index == None:
        return None
    #
    return dataSeries[index]


# --- Listed before datetime1 ----
# 不再使用区间计算法 20200220
def FindListedInstrument(database,
                         datetime2,
                         datetime1=None,
                         instrumentType="Stock",
                         noNewStock=False,
                         filter={}):

    df = FindListedInstrument_DataFrame(database,
                                        datetime2,
                                        datetime1=datetime1,
                                        instrument_type=instrumentType,
                                        no_new_stock=noNewStock,
                                        extra_filter=filter)
    documents = df.to_dict(orient='records')
    return documents


# 若指定DateTime2， 在DateTime2 没退市的股票
# 若指定DateTime1， 在DateTime1 已经上市交易的
def FindListedInstrument_DataFrame(database,
                                   datetime2,
                                   datetime1=None,
                                   instrument_type="Stock",
                                   no_new_stock=False, extra_filter={}):
    #
    filtered_instruments = database.Find("Instruments", instrument_type)
    if len(filtered_instruments) == 0:
        return pd.DataFrame()

    df = DocumentsToDataFrame(filtered_instruments)
    #
    convertFields = {}
    convertFields["datetime1"] = "DateTime1"
    convertFields["datetime2"] = "DateTime2"
    convertFields["symbol"] = "Symbol"
    convertFields["description"] = "Description"
    #
    for field, convertTo in convertFields.items():
        if field in df.columns:
            df.rename(columns={field: convertTo}, inplace=True)

    # 没退市， 这个时间仍然在市，正常交易
    df = df[df["DateTime2"] > datetime2]

    # 已上市，同时还必须满足 DateTime2 已经上市
    if no_new_stock:
        early_datetime = datetime2 - timedelta(days=365)
    else:
        early_datetime = datetime2

    # 一般不指定开始时间，如若指定，确保此时股票已经上市
    if datetime1:
        if no_new_stock:
            early_datetime = datetime1 - timedelta(days=365)
        else:
            early_datetime = datetime1

    df = df[df["DateTime1"] <= early_datetime]

    # 用外部filter进行过滤
    if len(extra_filter) > 0:
        for field, value in extra_filter.items():
            df = df[df[field] == value]

    #
    return df


# ---Fix bug, no "Excat" situation 2018-4-10 ---
def GetIndex(dataSeries, datetime, beginIndex, endIndex, recursive, option, datetimeField):
    length = endIndex - beginIndex + 1
    beginDatetime = dataSeries[beginIndex][datetimeField]
    endDatetime = dataSeries[endIndex][datetimeField]

    #-------------------------------
    #---Special Case---
    if datetime < beginDatetime:
        if option == "Previous":
            return beginIndex - 1
        if option == "Next":
            return beginIndex
        if option == "Exact":
            return -1
    if datetime > endDatetime:
        if option == "Previous":
            return endIndex
        if option == "Next":
            return endIndex + 1
        if option == "Exact":
            return -1

    if datetime == beginDatetime:
        return beginIndex

    if datetime == endDatetime:
        return endIndex

    #-------------------------------
    #if length == 1:
    #    if option == "Previous":
    #        return beginIndex - 1
    #    if option == "Next":
    #        return beginIndex
    #    #other "Exect"
    #    return None

    if length == 2:
        #---只剩下中间状态了---
        if option == "Previous":
            return beginIndex
        if option == "Next":
            return endIndex
        return -1

    else:
        midIndex = beginIndex + int((length * 0.5))
        tmpDateTime = dataSeries[midIndex][datetimeField]
        # tmpDateTime = tmpDateTime.replace(tzinfo=None)
        if datetime <= tmpDateTime:
            return GetIndex(dataSeries, datetime, beginIndex, midIndex, recursive+1, option, datetimeField)
        else:
            return GetIndex(dataSeries, datetime,midIndex, endIndex, recursive+1, option, datetimeField)

    return -1


def GenerateReportDate(year, period, isUTC=True):
    #
    if period == 1:
        reportDate = datetime(year, 3, 31)
    elif period == 2:
        reportDate = datetime(year, 6, 30)
    elif period == 3:
        reportDate = datetime(year, 9, 30)
    else:
        reportDate = datetime(year, 12, 31)
    #
    if isUTC:
        return ToUTCDateTime(reportDate)
    else:
        return reportDate


def MonthToPeriod(month):
    if month in [1, 2, 3]:
        return 1
    elif month in [4, 5, 6]:
        return 2
    elif month in [7, 8, 9]:
        return 3
    elif month in [10, 11, 12]:
        return 4
    else:
        print("Unknown Month", month)
        return np.nan

#
def GenerateNextReportDate(reportDate, offset=1):
    return GeneratePreviousReportDate(reportDate, offset=offset)


# offset -1 : 上一期
# offset +1 : 下一期
def GeneratePreviousReportDate(reportDate, offset=-1):
    year = reportDate.year
    month = reportDate.month
    period = MonthToPeriod(month)

    offsetPeriod = period + offset
    #
    quotient = offsetPeriod // 4
    remainder = offsetPeriod % 4

    targetYear = year + quotient
    if offsetPeriod >= 0: # 后移
        targetPeriod = remainder
        if remainder == 0:
            targetYear -= 1
            targetPeriod = 4
    else: # 前移
        targetPeriod = remainder
        if remainder == 0:
            targetYear -= 1
            targetPeriod = 4
    #
    return GenerateReportDate(targetYear, targetPeriod, isUTC=False)


# ---给定起始日期，获取中间包括的报告日日期，report date
def GenerateReportDates(datetime1, datetime2, asDate=False):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 3 and reportDate.day == 31)
            or
            (reportDate.month == 6 and reportDate.day == 30)
            or
            (reportDate.month == 9 and reportDate.day == 30)
            or
            (reportDate.month == 12 and reportDate.day == 31)):
            newReportDate = datetime(reportDate.year, reportDate.month, reportDate.day)
            if asDate:
                newReportDate = newReportDate.date()
            reportDateTimes.append(newReportDate)
        reportDate = reportDate + timedelta(days=1)

    return reportDateTimes


# ---获取每月第一天---
def GenerateBeginDateofMonth(datetime1, datetime2, as_date=False, is_utc=False):
    #
    if datetime1.tzinfo != None:
        is_utc = True
    else:
        is_utc = False
    #
    if is_utc:
        localDateTime1 = ToLocalDateTime(datetime1)
        localDateTime2 = ToLocalDateTime(datetime2)
    else:
        localDateTime1 = datetime1
        localDateTime2 = datetime2
    #
    datetimes = []
    for year in range(localDateTime1.year, localDateTime2.year + 1):
        for month in range(1, 13):
            beginDayofMonth = datetime(year, month, 1)
            #
            if is_utc:
                utcBeginDayofMonth = ToUTCDateTime(beginDayofMonth)
            else:
                utcBeginDayofMonth = beginDayofMonth
            #
            tmpDatetime = utcBeginDayofMonth
            if as_date:
                tmpDatetime = utcBeginDayofMonth.date()
            #
            if datetime1 > utcBeginDayofMonth:
                continue
            if datetime2 < utcBeginDayofMonth:
                continue
            datetimes.append(tmpDatetime)
    #
    return datetimes


def GenerateEndDateofMonth(datetime1, datetime2, as_date=False):
    # localDateTime1 = ToLocalDateTime(datetime1)
    # localDateTime2 = ToLocalDateTime(datetime2)
    localDateTime1 = datetime1
    localDateTime2 = datetime2
    # if isinstance(datetime1, datetime):
    #     localDateTime1 = datetime1.date()
    #     localDateTime2 = datetime2.date()
    #
    datetimes = []
    for year in range(localDateTime1.year, localDateTime2.year + 1):
        for month in range(1, 13):
            days = calendar.monthrange(year, month)
            if as_date:
                endDayofMonth = date(year, month, days[1])
            else:
                endDayofMonth = datetime(year, month, days[1])
            # endDayofMonth = endDayofMonth + timedelta(days=-1)
            if localDateTime1 > endDayofMonth:
                continue
            if localDateTime2 < endDayofMonth:
                continue
            datetimes.append(endDayofMonth)
    return datetimes


# ---5.1, 9.1 和 11.1(12.1)?
def GenerateReleaseDates(datetime1, datetime2):
    reportDateTimes = []
    reportDate = datetime1
    while reportDate <= datetime2:
        if ((reportDate.month == 5 and reportDate.day == 1)
            or
            (reportDate.month == 9 and reportDate.day == 1)
            or
            (reportDate.month == 11 and reportDate.day == 1)):
            reportDateTimes.append(reportDate)
        reportDate = reportDate + timedelta(days=1)
    return reportDateTimes


def DateTimeToReportDate(datetime2):

    reportDate = None
    if datetime2 < datetime(datetime2.year, 5, 1):
        #
        reportDate = datetime(datetime2.year-1, 9, 31) # last year Q3
    elif datetime2 < datetime(datetime2.year, 9, 1):
        #
        reportDate = datetime(datetime2.year, 3, 31)
    elif datetime2 < datetime(datetime2.year, 11, 1):
        #
        reportDate = datetime(datetime2.year, 6, 30)
    else:
        reportDate = datetime(datetime2.year, 9, 30)
    #
    return reportDate


def GenerateCloseTime(year, month, day):
    datetime1 = datetime(year, month, day)
    datetime1 = datetime1 + timedelta(hours=15)
    return ToUTCDateTime(datetime1)


def GenerateDateRange(datetime1, datetime2):
    datetimes = []
    dt = datetime1
    while dt <= datetime2:
        datetimes.append(dt)
        dt += timedelta(days=1)

    return datetimes



def LoadConstitutes(database, indexSymbol, datetime2=None):

    instruments = []
    instrumentList_TimeSeries = database.getDataSeries(indexSymbol + "_InstrumentList")

    # --- Old Fashion---
    # instrumentsList = instrumentSeries[instrumentList_TimeSeries.count()-1]

    if datetime2 == None:
        datetime2 = datetime.now()
    datetime2 = ToUTCDateTime(datetime2)

    instrumentsList = instrumentList_TimeSeries.Get(datetime2)

    for content in instrumentsList["Values"]:
        symbol = content["Symbol"]
        weight = content["Weight"]
        instruments.append({"Symbol": symbol, "Weight": weight})

    return instruments


#---读取证券列表---
def LoadInstruments(database, instrumentsByName, maxNum):
    instruments = []
    instrumentSeries = database.findAll("Instruments","Stock")
    i = -1
    for obj in instrumentSeries:
        i = i+1
        if maxNum != 0:
            if(i >= maxNum):
                break
        instrument = Instrument(obj["Symbol"], obj["Description"], obj["Type"]) #建立一个Instrument对象
        if "Industry" in obj["Properties"]:
            instrument.industry = obj["Properties"]["Industry"] #补充一个行业信息进去
        else:
            print("Symbol " + obj["Symbol"] + " Not Include Industry Info!!!")
        instrumentsByName[instrument.symbol] = instrument #把instrument们放进一个大容器（字典）
        instruments.append(instrument)
    return instruments


def LoadInstruments2(database, maxNum):
    instruments = []
    instrumentSeries = database.findAll("Instruments","Stock")
    i = -1
    for obj in instrumentSeries:
        i = i+1
        if maxNum != 0:
            if(i >= maxNum):
                break
        instruments.append(obj["Symbol"])
    return instruments


def MinDateTime():
    minDateTime = datetime(1900, 1, 1)
    # minDateTime = ToUTCDateTime(minDateTime)
    return minDateTime


def MaxDateTime():
    maxDateTime = datetime(2100, 1, 1)
    maxDateTime = ToUTCDateTime(maxDateTime)
    return maxDateTime


def MidNight(datetime1):
    #
    return datetime(datetime1.year, datetime1.month, datetime1.day) + timedelta(days=1)


# --- 2010-05-04 14:20:01.500 ---
# --- "%Y/%m/%d %H:%M:%S.%f" ---
# --- "%Y-%m-%d-%H" ---
def ParseDateTime(strDateTime, format = "%Y-%m-%d %H:%M:%S.%f"):
     return datetime.strptime(strDateTime, format)


def PlaySound():
    soundFile = 'd:\Sound\BeepLoop.wav'

    while(1):
        winsound.PlaySound(soundFile,winsound.SND_FILENAME)
        #winsound.PlaySound("SystemExit", winsound.SND_ALIAS)


def PageNavigation(page,button,maxLength):

    #page = parseInt(page)
    pageSize = 20
    maxPage = int(maxLength / pageSize) + 1

    if(button == "nextPage"):
        page += 1
    elif(button == "prePage"):
        page-=1
    elif(button == "lastPage"):
        page = maxPage
    elif(button == "firstPage"):
        page = 1

    if(page<1):
        page = 1
    if(page > maxPage):
        page = maxPage

    begin = (page - 1) * pageSize
    end = page * pageSize
    if (end > maxLength):
        end = maxLength - 1

    pageNavigation = {
        "Page":page,
        "Begin":begin,
        "End": end,
        "Size": pageSize,
        "MaxPage":maxPage}

    return pageNavigation


def StdDateTimeToTradeDate(stdDateTime):
    localDateTime = ToLocalDateTime(stdDateTime)
    return ToDate(localDateTime)


# ---DateTime convert to String of "2016-01-01"---
def ToDateString(date):
        #dt = datetime(int(date[0,3]),int(date[4,5]),int(date[6,7]))
        return date.strftime('%Y-%m-%d')


# ---DateTime convert to String of "20160101"---
def ToDateString2(date):
        #dt = datetime(int(date[0,3]),int(date[4,5]),int(date[6,7]))
        return date.strftime('%Y%m%d')

def ToDateString3(date):
 	#dt = datetime(int(date[0,3]),int(date[4,5]),int(date[6,7]))
    return str(date.year)+ "/" + str(date.month) + "/" + str(date.day)


def ToDateTimeString(date):
    s = date.strftime('%Y-%m-%d %H:%M:%S.%f')
    return s[:-3]


def ToDateTimeString2(date):
    return date.strftime('%Y-%m-%d %H:%M:%S.%f')


#---过滤Wind中毫秒脏数据现象---
def ToDateTimeString3(date):
    return date.strftime('%Y-%m-%d %H:%M:%S' + ".000")


def ToUTCDateTime(datetime1):
    #datetime2 = datetime1 + timedelta(hours=-8)
    #datetime2 = datetime2.replace(tzinfo=pytz.utc)

    # min = datetime(1950, 1, 1)
    # if datetime1 < min:
    #    datetime1 = min

    utc = pytz.utc
    datetime2 = datetime1.astimezone(utc)
    return datetime2


def ToLocalDateTime(datetime1):
    #datetime2 = datetime1 + timedelta(hours=+8)
    #datetime2 = datetime2.replace(tzinfo = pytz.timezone("Asia/Shanghai"))

    #min = datetime(1970, 1, 3)
    #if datetime1 < min:
    #    datetime1 = min

    local_Timezone = pytz.timezone("Asia/Shanghai")
    datetime2 = datetime1.astimezone(local_Timezone)
    return datetime2


def ToDate(datetime1):
    return datetime(datetime1.year, datetime1.month, datetime1.day)


def ToClosingDateTime(datetime1):
    datetime1 = ToLocalDateTime(datetime1)
    datetime2 = datetime(datetime1.year, datetime1.month, datetime1.day, 15, 0, 0)
    return ToUTCDateTime(datetime2)


def DateToDateTime(date1):
    return datetime.combine(date1, datetime.min.time())


#---Return 报告期 1,2,3,4---
def ReportPeriod(reportDate):
    if (reportDate.month == 3 and reportDate.day == 31):
        return 1;
        #modifiedDateTime = releaseDateTime + timedelta(seconds = 1)#防止第一季度报告和年报时间重合
    elif (reportDate.month == 6 and reportDate.day == 30):
        return 2;
    elif (reportDate.month == 9 and reportDate.day == 30):
        return 3;
    elif (reportDate.month == 12 and reportDate.day == 31):
        return 4;
    else:
        print("Unresolve Period");
        #print("Unresolve Period : " + symbol);
        return None


def ResampledDataSeries(dataSeries, datetimeSeries):
    resampledSeries = []
    i = 0
    for datetime0 in datetimeSeries:
        element = Find(dataSeries, datetime0)
        i = i + 1
        if element == None:
            continue
        resampledSeries.append(element)
    return resampledSeries


def ReadFolder(filepath):
    filenames = []
    pathDir =  os.listdir(filepath)
    for allDir in pathDir:
        child = os.path.join('%s%s' % (filepath, allDir))
        #print child.decode('gbk')
        filenames.append(allDir)

    return filenames


def TotalSize(o, handlers={}, verbose=False):
    """ Returns the approximate memory footprint an object and all of its contents.

    Automatically finds the contents of the following builtin containers and
    their subclasses:  tuple, list, deque, dict, set and frozenset.
    To search other containers, add handlers to iterate over their contents:

        handlers = {SomeContainerClass: iter,
                    OtherContainerClass: OtherContainerClass.get_elements}

    """
    dict_handler = lambda d: chain.from_iterable(d.items())
    all_handlers = {tuple: iter,
                    list: iter,
                    deque: iter,
                    dict: dict_handler,
                    set: iter,
                    frozenset: iter,
                   }
    all_handlers.update(handlers)     # user handlers take precedence
    seen = set()                      # track which object id's have already been seen
    default_size = getsizeof(0)       # estimate sizeof object without __sizeof__

    def sizeof(o):
        if id(o) in seen:       # do not double count the same object
            return 0
        seen.add(id(o))
        s = getsizeof(o, default_size)

        if verbose:
            print(s, type(o), repr(o), file=stderr)

        for typ, handler in all_handlers.items():
            if isinstance(o, typ):
                s += sum(map(sizeof, handler(o)))
                break
        return s

    return sizeof(o)


def SortDict(dict, reverse=False):
    sorted(dict.items(), key=lambda x: x[1], reverse=reverse)


def CreateFolder(fullPathname):
    existed = os.path.exists(fullPathname)
    if not existed:
        os.makedirs(fullPathname)
        print("Create Folder: " + fullPathname)
    else:
        #print("Folder Existed: " + fullPathname)
        pass


def GenerateTimeRange_Yearly(datetime1, datetime2, baseDate=datetime(2000, 5, 1)):
    datatimeRanges = []
    for year in range(datetime1.year, datetime2.year + 1):
        begin = datetime(year, baseDate.month, baseDate.day)
        end = datetime(year + 1, baseDate.month, baseDate.day)
        #
        if begin < datetime1 or end > datetime2:
            continue
        #
        datatimeRanges.append([begin, end])
    #
    return datatimeRanges


def GenerateTimeRange_HalfYear(datetime1, datetime2, baseDate=datetime(2000, 5, 1)):
    #
    curDateTime = datetime(datetime1.year, baseDate.month, baseDate.day)
    day = baseDate.day
    #
    datatimeRanges = []
    lastDateTime = curDateTime
    while curDateTime <= datetime2:
        month = curDateTime.month + 6
        if month > 12:
            curDateTime = datetime(curDateTime.year+1, month%12, day)
        else:
            curDateTime = datetime(curDateTime.year, month, day)

        if curDateTime > datetime2:
            break
        #
        datatimeRanges.append([lastDateTime, curDateTime])
        lastDateTime = curDateTime
    #
    return datatimeRanges


def GenerateTimeRange_Monthly(datetime1, datetime2):

    ret = []
    curDateTime = datetime1
    monthBegin = None
    monthEnd = None
    while curDateTime < datetime2:
        if curDateTime.day == 1:
            monthEnd = curDateTime + timedelta(days=-1)
            if monthBegin != None:
                ret.append([monthBegin, monthEnd])
            monthBegin = curDateTime
        curDateTime += timedelta(days=1)
    #
    return ret


def InstrumentsToSymbols(instruments):
    #
    symbols = []
    for instrument in instruments:
        symbols.append(instrument["Symbol"])
    #
    return symbols


def InstrumentToDateFrame(instruments):
    #
    symbols = []
    for instrument in instruments:
        symbols.append(instrument["Symbol"])
    #
    return pd.DataFrame(symbols, columns=["Symbol"])


def SymbolsToDateFrame(symbols):
    #
    return pd.DataFrame(symbols, columns=["Symbol"])


def DateTimeRange(datetime1, datetime2, datetimeField="StdDateTime"):
    #
    filter = {}
    filter[datetimeField] = {}
    if datetime1 != None:
        filter[datetimeField]["$gte"] = datetime1
    if datetime2 != None:
        filter[datetimeField]["$lte"] = datetime2
    if not filter[datetimeField]:
        del filter[datetimeField]
    #
    return filter


# {"Date": "Date", "Value": "Return"}
def DocumentsToDataFrame(documents, rename={}, drop=[], keep=[], index=None):
    data = []
    if len(documents) == 0:
        return pd.DataFrame()

    # ---确定存在哪些Field---
    # 抽样第一个和最后一个文档，用他们字段的并集
    possibleFields = list(documents[0].keys())
    for key, value in documents[-1].items():
        if key not in possibleFields:
            possibleFields.append(key)

    fields = []
    for field in possibleFields:
        # ---如果要索引，索引字段一定要保留---
        if field == index:
            fields.append(field)
            continue
        #
        if len(drop) > 0 and field in drop:
            continue
        if len(keep) > 0 and field not in keep:
            continue
        #
        fields.append(field)

    # ---Content---
    for d in documents:
        row = []
        for f in fields:
            value = d.get(f)
            if isinstance(value, decimal.Decimal):
                value = float(value)
            row.append(value)
        data.append(row)

    # ---Build DataFrame---
    dfPf = pd.DataFrame(data, columns=fields)
    # print(dfPf.head())
    #
    if index !=None:
        dfPf.set_index(index, inplace=True)

    # ---有些需要转换格式---
    # 强制将Date变为Datetime会带来很多问题，比如Date之间不能比较和加减运算
    # convertToDateTime = ["date", "Date"]
    # for field in convertToDateTime:
    #     if field in dfPf.columns:
    #         dfPf[field] = pd.to_datetime(dfPf[field])

    # ---Rename---
    dfPf.rename(columns=rename, inplace=True)

    #
    return dfPf

#
def FindReportDate(target_datetime, offset=0):
    days = np.abs(offset * 95) + 95
    days = int(days)
    if days < 365:
        days = 365
    #
    if offset <= 0: # 向前找最近的一个
        datetime1 = target_datetime - timedelta(days=days)
        datetime2 = target_datetime
        report_dates = GenerateReportDates(datetime1, datetime2)
        return report_dates[offset-1]

    # elif offset < 0: # previous 前序寻找
    #     datetime1 = target_datetime - timedelta(days=days)
    #     datetime2 = target_datetime - timedelta(days=1) # 避免当天是Report Date的情况
    #     report_dates = GenerateReportDates(datetime1, datetime2)
    #     return report_dates[offset-1]
    #
    else: # "Next"
        datetime1 = target_datetime + timedelta(days=1) # 避免当天是Report Date的情况
        datetime2 = target_datetime + timedelta(days=days)
        report_dates = GenerateReportDates(datetime1, datetime2)
        return report_dates[offset-1]


def Find_Recent_Month_Date(target_datetime, offset_month=0, begin_date=True):
    #
    days = np.abs(offset_month * 31) + 31
    days = int(days)
    #
    if offset_month <= 0: # 向前找最近的一个
        datetime1 = target_datetime - timedelta(days=days)
        datetime2 = target_datetime
        if begin_date:
            month_dates = GenerateBeginDateofMonth(datetime1, datetime2)
        else:
            month_dates = GenerateEndDateofMonth(datetime1, datetime2)
        return month_dates[offset_month-1]
    #
    else: # "Next"
        datetime1 = target_datetime + timedelta(days=1) # 避免当天是Report Date的情况
        datetime2 = target_datetime + timedelta(days=days)
        if begin_date:
            month_dates = GenerateBeginDateofMonth(datetime1, datetime2)
        else:
            month_dates = GenerateEndDateofMonth(datetime1, datetime2)
        return month_dates[offset_month-1]


def Find_Recent_Released_ReportDate(datetime2):
    year = datetime2.year
    #
    if datetime2.month >= 11:
        report_date = datetime(year, 9, 30)
    elif datetime2.month >= 9:
        report_date = datetime(year, 6, 30)
    elif datetime2.month >= 5:
        report_date = datetime(year, 3, 31)
    else:
        report_date = datetime(year-1, 9, 30)
    #
    return report_date


def Find_Recent_Yearly_ReportDate(datetime2):
    year = datetime2.year
    #
    if datetime2.month >= 5:
        report_date = datetime(year-1, 12, 31)
    else:
        report_date = datetime(year-2, 12, 31)
    #
    return report_date


def FindReleaseDate(datetime2, option="Previous"):
    #
    if option == "Previous":
        datetime1 = datetime2 + timedelta(days=-365)
        reportDates = GenerateReleaseDates(datetime1, datetime2)
        tmpDateTime = reportDates[-1]
    elif option == "Next":
        datetime1 = datetime2
        datetime2 = datetime1 + timedelta(days=365)
        reportDates = GenerateReleaseDates(datetime1, datetime2)
        tmpDateTime = reportDates[0]
    else:
        return None
    #
    tmpDateTime = datetime(tmpDateTime.year, tmpDateTime.month, tmpDateTime.day)
    return tmpDateTime


# 假设当前日期为发布日，找到可能对应的报告日
def FromReleaseDateToFindReportDate(datetime2):
    #
    reportDateTimes = []
    lastYear = datetime2.year -1
    year = datetime2.year

    # --- 自动按照当前日期，决定是否补齐---
    if datetime2.month >= 10:
        reportDateTimes.append(datetime(year, 9, 30))
    elif datetime2.month >= 7:
        reportDateTimes.append(datetime(year, 6, 30))
    elif datetime2.month >= 3:
        reportDateTimes.append(datetime(year, 3, 31))
        reportDateTimes.append(datetime(lastYear, 12, 31))
    elif datetime2.month >= 1:
        reportDateTimes.append(datetime(lastYear, 12, 31))
    #
    return reportDateTimes


def FromDateRangeToFindReportDate(datetime1, datetime2):
    #
    reportDateTimes = []
    datetime0 = datetime1
    while datetime0 < datetime2:
        tmpDateTimes = FromReleaseDateToFindReportDate(datetime0)
        for tmp in tmpDateTimes:
            if tmp not in reportDateTimes:
                reportDateTimes.append(tmp)
        datetime0 += timedelta(days=1)
    #
    return reportDateTimes


def Generate_CalenderDays_DataFrame(datetime1, datetime2, date_field_name="Date", date_index=False):
    dt = datetime1
    data = []
    while dt <= datetime2:
        data.append(dt)
        dt += timedelta(days=1)
    df = pd.DataFrame(data, columns=[date_field_name])
    #
    df[date_field_name] = pd.to_datetime(df[date_field_name]) # 不转换本身就是DateTime
    #
    if date_index:
        df.set_index(date_field_name, inplace=True)
    #
    return df


def Generate_TradingDays_DataFrame(database, datetime1, datetime2, date_field_name="", date_index=False):
    #
    df = database.GetDataFrame("DailyBar", "Index", {"symbol":"000001.SH"}, ["date"], datetime1, datetime2)
    # print(df)
    #
    if date_field_name != "":
        df.rename(columns={"date": date_field_name})
    else:
        date_field_name = "date"
    #
    if date_index:
        df.set_index(date_field_name, inplace=True)
    #
    # print(df)
    return df


def GenerateCalenderDays(datetime1, datetime2):
    dt = datetime1
    data = []
    while dt <= datetime2:
        data.append(dt)
        dt += timedelta(days=1)
    #
    return data


def GenerateWeekEndDays(datetime1, datetime2, weekend=6):
    df = Generate_CalenderDays_DataFrame(datetime1, datetime2)
    AddWeekColumn(df)
    # print(df)
    df["WeekDay"] = df["Date"].dt.dayofweek
    df = df[df["WeekDay"]==weekend]
    # print(df)
    return df

# 修正
def WeekNumToNumeric(year, month, weekNum):
    # 1月出现52Week
    if month == 1 and weekNum == 52:
        return (year - 1) * 100 + weekNum
    elif month == 12 and weekNum == 1:  #
        return (year + 1) * 100 + weekNum
    else:
        return year * 100 + weekNum


def AddMonthColumn(df, date_field="Date"):
    df[date_field] = pd.to_datetime(df[date_field])
    df["Year"] = df[date_field].dt.year
    df["Month"] = df[date_field].dt.month
    df["Month"] = df["Year"] * 100 + df["Month"]
    # df["Month"] = df["Year"].astype(str).str.cat(df["Month"].astype(str),sep="-")


def AddWeekColumn(df, date_field="Date"):
    def Func(x):
        year = x["Year"]
        month = x["Month"]
        weekNum = x["WeekNum"]

        # 1月出现52Week
        if month == 1 and weekNum == 52:
            return (year - 1) * 100 + weekNum
        elif month == 12 and weekNum == 1: #
            return (year + 1) * 100 + weekNum
        else:
            return year * 100 + weekNum

    #
    df[date_field] = pd.to_datetime(df[date_field])
    df["WeekNum"] = df[date_field].dt.weekofyear
    df["Year"] = df[date_field].dt.year
    df["Month"] = df[date_field].dt.month
    df["WeekDay"] = df[date_field].dt.dayofweek
    # df["Week"] = df["Year"].astype(str).str.cat(df["WeekNum"].astype(str))
    # print(df)

    # df["Week"] = df.apply(Func, axis=1)
    # df["Week"] = df.apply(lambda x: Func(x), axis=1)
    df["Week"] = df.apply(lambda x: WeekNumToNumeric(x["Year"], x["Month"], x["WeekNum"]), axis=1)

    #
    df.drop(columns=["WeekNum", "Year", "Month"], inplace=True)


def DateToWeekNum(dt):
    df = pd.DataFrame(data=[[dt]], columns=["Date"])
    df["WeekNum"] = df["Date"].dt.weekofyear
    year = df.iloc[0]["Date"].year
    month = df.iloc[0]["Date"].month
    weekNum = df.iloc[0]["WeekNum"]
    week = WeekNumToNumeric(year, month, weekNum)
    #
    return week


def ParseExcelDatetime(input, default=datetime(1900, 1, 1)):
    if input == "":
        return default
    return datetime(*xldate_as_tuple(input,0))


# 随机抽取Instrument 测试用
def RandomInstruments(database, datetime1, num=100, seed=0):
    #
    totalInstruments = FindListedInstrument(database, datetime1)
    count = len(totalInstruments)

    #
    random.seed(seed)
    l = list(range(0, count))
    r = random.sample(l, num)

    #
    instruments = []
    for i in r:
        instruments.append(totalInstruments[i])
    #
    return instruments


# Symbols 转换为 Instruments
def SymbolsToInstruments(database, symbols):

    filter = {}
    filter["$or"] = []
    for symbol in symbols:
        filter["$or"].append({"Symbol": symbol})
    #
    instruments = database.Find("Instruments", "Stock", filter)
    return instruments


def GetTradingDays(database, datetime1, datetiem2, backforword1Day=False):
    #
    filter = {"Symbol": "000001.SH"}
    filter["Date"] = {">=": datetime1, "<=": datetiem2}
    bmDataSeries = database.Find("DailyBar", "Index", filter)
    #
    if not backforword1Day:
        return bmDataSeries

    #
    found = False
    datetime0 = None
    for i in range(10):
        datetime0 = datetime1 + timedelta(days=-1 * (i+1))
        filter["Date"][">="] = datetime0
        bmDataSeries2 = database.Find("DailyBar", "Index", filter)
        # bmDataSeries2 = database.Find("Factor", "DailyReturn", filter)
        if len(bmDataSeries2) > len(bmDataSeries):
            datetime0 = bmDataSeries2[0]["date"]
            found = True
            break
    #
    if not found:
        print("Not Find Correct DateTime1 !!!")
    #
    return bmDataSeries2


def GetTradingDays_DataFrame(database, datetime1, datetiem2, backforword1Day=False):
    #
    documents = GetTradingDays(database, datetime1, datetiem2, backforword1Day=backforword1Day)
    df = DocumentsToDataFrame(documents, keep=["Date","date"], rename={"date": "Date"})
    return df

# 找到最近的星期X
# x : 星期x
def Find_Recent_Weekday(datetime2, x, previous=True):
    #
    target_x = x-1
    datetime2_new = datetime2

    # weekday 0-6，星期一到星期日
    weekday = datetime2.weekday()
    while weekday != target_x:
        if previous:
            datetime2_new = datetime2_new + timedelta(days=-1)  # 往前推
        else:
            datetime2_new = datetime2_new + timedelta(days=1)  # 往后推
        #
        weekday = datetime2_new.weekday()
    #
    return datetime2_new


def Find_Last_Month_End_Day(update_datetime):
    datetime_tmp = datetime(update_datetime.year, update_datetime.month, 1)
    return datetime_tmp - timedelta(days=1)


# 确定日期
def Find_Recent_Date(database, database_name, table_name, update_datetime):
    #
    s_update_datetime = update_datetime.strftime('%Y-%m-%d')
    sql_str = "select max(date) from " + database_name + "." + table_name + " where date <=" + "'" + s_update_datetime + "'"
    # print(sql_str)
    max_date = database.ExecuteSQL("derivative_data", sql_str)
    recent_date = max_date[0][0]
    return recent_date


if __name__ == '__main__':
    #
    update_datetime = datetime(2019,1,1)
    a = Find_Last_Month_End_Day(update_datetime)

    #
    datetime1 = datetime(2015, 5, 30)
    t2 = FindReportDate(datetime1, 0)
    t1 = FindReportDate(datetime1, -1)
    t3 = FindReportDate(datetime1, 1)
    #
    # for diff in [8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8]:
    #     t = GeneratePreviousReportDate(datetime1, diff)
    #     t2 = FindReportDate(datetime1, diff)
    #     print(diff, t, t2)

    datetime1 = datetime(2020, 12, 15)
    for diff in [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12]:
        t = Find_Recent_Month_Date(datetime1, diff, begin_date=False)
        print(diff, t)